using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

using TInput = System.String;
using TOutput = System.String;
using NDF.XnaLibrary.Models;

namespace NDF.SkinnedModelProcessor
{
	[ContentProcessor(DisplayName = "Skinned Effect Processor")]
	public class SkinnedEffectProcessor : ModelProcessor
	{
		public override ModelContent Process(NodeContent input,
											 ContentProcessorContext context)
		{
			SkinnedProcessorHelper.ValidateMesh(input, context, null);

			// Find the skeleton.
			BoneContent skeleton = MeshHelper.FindSkeleton(input);

			if (skeleton == null)
				throw new InvalidContentException("Input skeleton not found.");

			// We don't want to have to worry about different parts of the model being
			// in different local coordinate systems, so let's just bake everything.
			SkinnedProcessorHelper.FlattenTransforms(input, skeleton);

			// Read the bind pose and skeleton hierarchy data.
			IList<BoneContent> bones = MeshHelper.FlattenSkeleton(skeleton);

			if (bones.Count > SkinnedEffect.MaxBones)
			{
				throw new InvalidContentException(string.Format(
					"Skeleton has {0} bones, but the maximum supported is {1}.",
					bones.Count, SkinnedEffect.MaxBones));
			}

			List<Matrix> bindPose = new List<Matrix>();
			List<Matrix> inverseBindPose = new List<Matrix>();
			List<int> skeletonHierarchy = new List<int>();
			Dictionary<string, int> boneIndices = new Dictionary<string, int>();

			foreach (BoneContent bone in bones)
			{
				bindPose.Add(bone.Transform);
				inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform));
				skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent));
				boneIndices.Add(bone.Name, boneIndices.Count);
			}

			// Chain to the base ModelProcessor class so it can convert the model data.
			ModelContent model = base.Process(input, context);

			model.Tag = new SkinnedModelInfo(bindPose, inverseBindPose, skeletonHierarchy, boneIndices);

			return model;
		}
	}
}